﻿namespace MetaProperties.Core
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    using MetaProperties.Framework;

    /// <summary>
    /// Utility methods for dealing with MetaProperties.
    /// </summary>
    public static class MetaPropertyUtils
    {
        /// <summary>
        /// The first valid postfix for a MetaProperty.
        /// </summary>
        public const string MetaPropertyPostfix1 = "Property";

        /// <summary>
        /// The second valid postfix for a MetaProperty.
        /// </summary>
        public const string MetaPropertyPostfix2 = "MetaProperty";

        /// <summary>
        /// Determines whether the given property name relates to a MetaProperty.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>
        ///     <c>true</c> if the specified property name relates to a MetaProperty; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsMetaProperty(string propertyName)
        {
            // No need to check for MetaPropertyPostfix2 as it is a subset of 1.
            return propertyName.EndsWith(MetaPropertyUtils.MetaPropertyPostfix1)
                && propertyName != MetaPropertyUtils.MetaPropertyPostfix1
                && propertyName != MetaPropertyUtils.MetaPropertyPostfix2;
        }

        /// <summary>
        /// Gets the name of the property associated with the MetaProperty.
        /// </summary>
        /// <param name="metaPropertyName">The name of the MetaProperty.</param>
        /// <returns>The name of the property.</returns>
        public static string GetPropertyName(string metaPropertyName)
        {
            string ending = null;
            if (IsMetaProperty(metaPropertyName))
            {
                // Must check for MetaPropertyPostfix2 first.
                if (metaPropertyName.EndsWith(MetaPropertyUtils.MetaPropertyPostfix2))
                {
                    ending = MetaPropertyUtils.MetaPropertyPostfix2;
                }
                else
                {
                    ending = MetaPropertyUtils.MetaPropertyPostfix1;
                }
            }

            if (ending == null)
            {
                return metaPropertyName;
            }
            else
            {
                return metaPropertyName.Substring(
                        0,
                        metaPropertyName.Length - ending.Length);
            }
        }

        /// <summary>
        /// Initializes the MetaProperties of the property source.
        /// </summary>
        /// <param name="propertySource">The property source.</param>
        /// <returns>The stream of initialized MetaProperties.</returns>
        public static IEnumerable<IMetaProperty> InitializeMetaProperties(IMetaPropertyHost propertySource)
        {
            if (propertySource == null)
            {
                throw new ArgumentNullException("propertySource");
            }

            // For each field...
            PropertyInfo[] properties = GetPublicProperties(propertySource);

            foreach (PropertyInfo property in properties)
            {
                IMetaProperty metaProperty = InitializeMetaProperty(propertySource, property);

                if (metaProperty != null)
                {
                    yield return metaProperty;
                }
            }
        }

        /// <summary>
        /// Gets the public properties of the property source.
        /// </summary>
        /// <param name="propertySource">The property source.</param>
        /// <returns>The information for each public property.</returns>
        private static PropertyInfo[] GetPublicProperties(IMetaPropertyHost propertySource)
        {
            // We only search for public properties (not internal)
            // because anything more would require extra privilages
            // (and wouldn't be supported by silverlight).
            return propertySource.GetType()
                    .GetProperties(BindingFlags.Instance | BindingFlags.Public);
        }

        /// <summary>
        /// Initializes the MetaProperty.
        /// </summary>
        /// <param name="propertySource">The property source.</param>
        /// <param name="propertyInfo">The property info.</param>
        /// <returns>The initialized IMetaProperty.</returns>
        private static IMetaProperty InitializeMetaProperty(
            IMetaPropertyHost propertySource,
            PropertyInfo propertyInfo)
        {
            // ... if it is return the correct type and if the name is formatted correctly ...
            if (typeof(IMetaProperty).IsAssignableFrom(propertyInfo.PropertyType)
                && MetaPropertyUtils.IsMetaProperty(propertyInfo.Name))
            {
                // ... if it is not an indexer ...
                ParameterInfo[] parameters = propertyInfo.GetIndexParameters();
                if (parameters == null || parameters.Length == 0)
                {
                    // ... if it is assigned ...
                    IMetaProperty metaProperty = (IMetaProperty)propertyInfo.GetValue(propertySource, null);
                    if (null != metaProperty)
                    {
                        InitialiseMetaProperty(propertySource, propertyInfo, metaProperty);
                        return metaProperty;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Initialises the MetaProperty.
        /// </summary>
        /// <param name="propertySource">The property source.</param>
        /// <param name="propertyInfo">The property info.</param>
        /// <param name="metaProperty">The MetaProperty.</param>
        private static void InitialiseMetaProperty(
            IMetaPropertyHost propertySource,
            PropertyInfo propertyInfo,
            IMetaProperty metaProperty)
        {
            string propertyName = MetaPropertyUtils.GetPropertyName(propertyInfo.Name);

            IEnumerable<Attribute> attributes = propertyInfo.GetCustomAttributes(true).OfType<Attribute>();

            var initializationInfo = new MetaPropertyConfiguration(
                propertySource,
                propertyName,
                attributes);

            metaProperty.Initialize(initializationInfo);
        }
    }
}
